﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Data.Linq;
using System.Xml;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using uk.ac.soton.ses.DAL;
using System.Diagnostics;
using System.Data.EntityClient;

namespace uk.ac.soton.ses
{
 
    public class ExperimentMetadataContainerXML : ExperimentMetadataContainer
    {
        /// <summary>
        /// The name of the folder that will be created within an experiment folder and used to hold metadata, loaded from app.config
        /// </summary>
        private static readonly string _trackingFolderName = HDCConfiguration.Instance.ConfigurationOptions.FileSystemWatcherOptions.TrackingFolderName;

        /// <summary>
        /// The name of the XML file which records metadata about the files within an experiment folder, loaded from the app.config
        /// </summary>
        private static readonly string _trackingFileName = HDCConfiguration.Instance.ConfigurationOptions.FileSystemWatcherOptions.TrackingFileName;

        /// <summary>
        /// The name of the HTML file which redirects to the experiment's details page
        /// </summary>
        private static readonly string _detailsPageRedirFileName = HDCConfiguration.Instance.ConfigurationOptions.FileSystemWatcherOptions.DetailsPageRedirFileName;

        /// <summary>
        /// The path to the actual XML tracking file in this intance of an experiment folder
        /// </summary>
        private string _trackingFilePath;

        /// <summary>
        /// The path to the actual XML tracking folder in this intance of an experiment folder
        /// </summary>
        private string _trackingFolderPath;

        /// <summary>
        /// The metadata stored in the tracking file
        /// </summary>
        private ExperimentMetadataAndHistory _xmlMetadata;

        public ExperimentHistory History { get { return _xmlMetadata.History; } }


        /// <summary>
        /// Constructor: Populates the metadata based on the XML file stored at the given path. If the file does not exist, it can only be created from an ExperimentMetadataContainerDB object
        /// </summary>
        /// <param name="experimentBasePath">The path to the experiment containing an XML file</param>
        public ExperimentMetadataContainerXML(string experimentBasePath):base()
        {
            string trackingFilePath = GetTrackingFilePath(experimentBasePath);
            if (!File.Exists(trackingFilePath))
            {
                throw new FileNotFoundException(string.Format("Tracking file {0} not found", trackingFilePath));
            }
            else
            {
                this._trackingFilePath = trackingFilePath;
                this._trackingFolderPath = Path.GetDirectoryName(this._trackingFilePath);
                this._xmlMetadata = ExperimentMetadataContainerXML.GetMetadataFromTrackingFile(this._trackingFilePath);
                if(!ExperimentMetadataContainerXML.MetadataIsValid(this._xmlMetadata))
                {
                    throw new ExperimentMetadataContainerException("Tracking file metadata not valid");
                }
                base._experiment = this._xmlMetadata.Experiment;

                base._experimentDataFiles = new List<ExperimentDataFileMetadataContainer>();
                base._experimentDataFiles.AddRange(this._xmlMetadata.ExperimentDataFiles.Select(df=>new ExperimentDataFileMetadataContainerXML(df, this)));
            }
        }

        /// <summary>
        /// Copy experiment data into the history section of an XML file. This is used, for example, when an experiment is renamed and the old name needs saving
        /// </summary>
        /// <param name="exp">The experiment data to save</param>
        /// <param name="action">Why the data was changed</param>
        public void SaveToHistory(ExperimentBO exp, ExperimentMetadataAction action)
        {
            this._xmlMetadata.History.ExperimentHistoryEntries.Add(new ExperimentHistoryEntry() { Action = action, TimeStamp = DateTime.Now, Experiment = exp.Copy() });
        }

        /// <summary>
        /// Copy experiment_data_file data into the history section of an XML file. This is used, for example, when a data file is renamed and the old name needs saving
        /// </summary>
        /// <param name="expdatafile">The experiment_data_file data to save</param>
        /// <param name="action">Why the data was changed</param>
        public void SaveToHistory(ExperimentDataFileBO expdatafile, ExperimentMetadataAction action)
        {
            //ExperimentDataFileHistory entries are grouped by file (found by database ID). We try and find an existing record in the history and add to that, otherwise we'll create a new entry
            ExperimentDataFileHistory datafileHistory = this._xmlMetadata.History.ExperimentDataFilesHistory.SingleOrDefault(datafile => datafile.ID == expdatafile.ID);
            if (datafileHistory == null)
            {
                datafileHistory =
                    new ExperimentDataFileHistory()
                    {
                        ID = expdatafile.ID,
                        ExperimentDataFileHistoryEntries = new List<ExperimentDataFileHistoryEntry>()
                    };
                this._xmlMetadata.History.ExperimentDataFilesHistory.Add(datafileHistory);
            }
            datafileHistory.ExperimentDataFileHistoryEntries.Add(new ExperimentDataFileHistoryEntry() { Action = action, TimeStamp = DateTime.Now, ExperimentDataFile = expdatafile.Copy() });
        }

        /// <summary>
        /// Changs the name of an experiment, optionally saving a copy of the experiment data to history
        /// </summary>
        /// <param name="newName">The new name of the experiment</param>
        /// <param name="newExperimentBasePath">The new base path of the experiment</param>
        public override void RenameExperiment(string newName, string newExperimentBasePath)
        {
            this.SaveToHistory(base.Experiment, ExperimentMetadataAction.Updated);
            base.RenameExperiment(newName, newExperimentBasePath);
        }

        public bool DeleteExperimentDataFile(int id)
        {
            if(base._experimentDataFiles.Count(df=>df.ExperimentDataFile.ID==id)==1)
            {
                ExperimentDataFileBO datafileToDelete = base._experimentDataFiles.Single(df => df.ExperimentDataFile.ID == id).ExperimentDataFile;
                this.SaveToHistory(datafileToDelete, ExperimentMetadataAction.Deleted);
                base._experimentDataFiles.RemoveAll(df => df.ExperimentDataFile.ID == id);
                return true;
            }
            else
            {
                return false;
            }
        }
        public bool RenameExperimentDataFile(int id, string newName, string newExperimentBasePath)
        {
            if (base._experimentDataFiles.Count(df => df.ExperimentDataFile.ID == id) == 1)
            {
                ExperimentDataFileMetadataContainer fileToRename = base._experimentDataFiles.SingleOrDefault(df => df.ExperimentDataFile.ID == id) as ExperimentDataFileMetadataContainer;
                if (fileToRename != null)
                {
                    fileToRename.RenameExperimentDataFile(newName, newExperimentBasePath);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Static method to check whether an XML experiment tracking file exists in a folder
        /// </summary>
        /// <param name="experimentDirectoryPath">The folder to check for an XML tracking file</param>
        /// <returns>True if the XML exists, otherwise false</returns>
        public static bool MetadataExists(string experimentDirectoryPath)
        {
            return File.Exists(GetTrackingFilePath(experimentDirectoryPath));
        }

        /// <summary>
        /// Static method to check whether an HTML experiment details redirect file exists in a folder
        /// </summary>
        /// <param name="experimentDirectoryPath">The folder to check for an HTML experiment details redirect file</param>
        /// <returns>True if the file exists, otherwise false</returns>
        public static bool DetailsPageRedirFileExists(string experientDirectoryPath)
        {
            return File.Exists(GetDetailsPageRedirFilePath(experientDirectoryPath));
        }

        public static bool MetadataIsValid(ExperimentMetadataAndHistory metadata)
        {
            if (metadata.Experiment.ID <= 0)return false;
            if (metadata.ExperimentDataFiles.Any(df=>df.ID<=0 || df.ExperimentID!=metadata.Experiment.ID)) return false;
            return true;
        }

        /// <summary>
        /// Generates the full path to the experiment metadata XML file's parent folder
        /// </summary>
        /// <param name="experimentDirectoryPath">The full path to the experiment's folder</param>
        /// <returns>The full path to the experiment's metadata file's folder</returns>
        public static string GetTrackingFolderPath(string experimentDirectoryPath)
        {
            if (_trackingFolderName.Length > 0)
                return Path.Combine(experimentDirectoryPath, _trackingFolderName);
            else
                return experimentDirectoryPath;
        }

        /// <summary>
        /// Generates the full path the experiment metadata XML file
        /// </summary>
        /// <param name="experimentDirectoryPath">The full path to the experiment's folder</param>
        /// <returns>The full path to the experiment's metadata file</returns>
        public static string GetTrackingFilePath(string experimentDirectoryPath)
        {
            return Path.Combine(GetTrackingFolderPath(experimentDirectoryPath), _trackingFileName);
        }

        /// <summary>
        /// Generates the full path the details page HTML file
        /// </summary>
        /// <param name="experimentDirectoryPath">The full path to the experiment's folder</param>
        /// <returns>The full path to the experiment's details page HTML file</returns>
        public static string GetDetailsPageRedirFilePath(string experimentDirectoryPath)
        {
            return Path.Combine(GetTrackingFolderPath(experimentDirectoryPath), _detailsPageRedirFileName);
        }

        /// <summary>
        /// Parse the XML metadata file and populate fields in the class
        /// </summary>
        /// <param name="trackingFilePath">The full path to an existing XML file</param>
        /// <returns>An object containing the data deserialized from the XML file</returns>
        private static ExperimentMetadataAndHistory GetMetadataFromTrackingFile(string trackingFilePath)
        {
            using (FileStream fs = File.OpenRead(trackingFilePath))
            {
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(ExperimentMetadataAndHistory));
                ExperimentMetadataAndHistory xmlExperiment = (ExperimentMetadataAndHistory)xmlSerializer.Deserialize(fs);
                return xmlExperiment;
            }
        }

        /// <summary>
        /// Saves Experiment Metadata from a database as XML and returns an ExperimentMetadataContainerXML object
        /// </summary>
        /// <param name="dbMetadata">The metadata loaded from the database</param>
        /// <param name="overwrite">Whether to overwrite the XML file if it already exists</param>
        /// <returns>An object containing the data deserialized from the XML file</returns>
        public static ExperimentMetadataContainerXML Create(ExperimentMetadataContainerDB dbMetadata, bool overwrite)
        {
            string experimentDirectory = dbMetadata.Experiment.DefaultBasePath;
            string trackingFileFolder = GetTrackingFolderPath(experimentDirectory);
            string trackingFilePath = GetTrackingFilePath(experimentDirectory);
            string detailsPageRedirFilePath = GetDetailsPageRedirFilePath(experimentDirectory);

            if (!Directory.Exists(experimentDirectory))
            {
                throw new DirectoryNotFoundException(string.Format("Experiment {0} not found", experimentDirectory));
            }
            if (!Directory.Exists(trackingFileFolder))
            {
                DirectoryInfo newFolderInfo = Directory.CreateDirectory(trackingFileFolder);
                newFolderInfo.Attributes |= FileAttributes.Hidden;
            }
            ExperimentMetadataAndHistory xmlData = new ExperimentMetadataAndHistory();
            xmlData.Experiment = dbMetadata.Experiment.Copy();
            xmlData.ExperimentDataFiles =
                dbMetadata.ExperimentDataFiles
                .Select(datafile => datafile.ExperimentDataFile.Copy())
                .ToList();
                    
            ExperimentMetadataContainerXML.SaveMetadata(xmlData, trackingFilePath, overwrite);
            ExperimentMetadataContainerXML.SaveDetailsPageRedirFile(dbMetadata.Experiment.ID, detailsPageRedirFilePath, overwrite);
            return new ExperimentMetadataContainerXML(experimentDirectory);
        }
        
        /// <summary>
        /// Save the ExperimentMetadataAndHistory object to the XML tracking file specified
        /// </summary>
        /// <param name="xmlMetadata">The ExperimentMetadataAndHistory object to save to XML</param>
        /// <param name="trackingFilePath">The file name to use</param>
        /// <param name="overwrite">Whether to overwrite it if it exists. If this is false and the file exists, an Exception will be thrown</param>
        public static void SaveMetadata(ExperimentMetadataAndHistory xmlMetadata, string trackingFilePath, bool overwrite)
        {
            using (FileStream fs = new FileStream(trackingFilePath, overwrite ? FileMode.Create : FileMode.CreateNew))
            {
                XmlTextWriter xmlwriter = new XmlTextWriter(fs, Encoding.UTF8);
                xmlwriter.Formatting = Formatting.Indented;
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(ExperimentMetadataAndHistory));
                xmlSerializer.Serialize(xmlwriter, xmlMetadata);
            }
        }

        /// <summary>
        /// Save the ExperimentMetadataAndHistory object to the XML tracking file specified
        /// </summary>
        /// <param name="xmlMetadata">The ExperimentMetadataAndHistory object to save to XML</param>
        /// <param name="trackingFilePath">The file name to use</param>
        /// <param name="overwrite">Whether to overwrite it if it exists. If this is false and the file exists, an Exception will be thrown</param>
        public static void SaveDetailsPageRedirFile(int experimentID, string detailsPageRedirFilePath, bool overwrite)
        {
            string detailsPageUrl;
            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(ExperimentMetadataContainerDB.GetEntityConnectionString()))
            {
                Experiment exp = dbcontext.GetExperimentByID(experimentID);
                detailsPageUrl = exp.GetDetailsWebPage(dbcontext);
            }
            string detailsPageContents = string.Format(@"
<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv='refresh' content='2;url={0}'>
        <script type='text/javascript'>
            var timerTimeout=setTimeout(function(){{window.location.href = '{0}'}},2000);
        </script>
        <title>Page Redirection</title>
    </head>
    <body>
        Data set details page is at <a href='{0}'>{0}</a>. If you aren't automatically redirected, please follow <a href='{0}'>this link</a>
    </body>
</html>
", detailsPageUrl);

            using (FileStream fs = new FileStream(detailsPageRedirFilePath, overwrite ? FileMode.Create : FileMode.CreateNew))
            using(StreamWriter sw = new StreamWriter(fs,Encoding.UTF8))
            {
                sw.Write(detailsPageContents);
                sw.Flush();
            }
        }
        
        /// <summary>
        /// Save changes made to the Experiment and ExperimentDataFiles to the XML tracking file
        /// </summary>
        public override void SaveChanges()
        {
            base.SaveChanges();

            this._xmlMetadata.Experiment = base._experiment;
            this._xmlMetadata.ExperimentDataFiles = base._experimentDataFiles.Select(df=>df.ExperimentDataFile).ToList();
            ExperimentMetadataContainerXML.SaveMetadata(this._xmlMetadata, this._trackingFilePath, true);
        }

        public override ExperimentDataFileMetadataContainer AddExperimentDataFile(ExperimentDataFileBO experimentDataFile)
        {
            this._xmlMetadata.ExperimentDataFiles.Add(experimentDataFile.Copy());
            ExperimentDataFileMetadataContainerXML newFile = new ExperimentDataFileMetadataContainerXML(experimentDataFile.Copy(), this);
            base._experimentDataFiles.Add(newFile);
            return newFile;
        }

        public void BackupMetadata()
        {
            string dateformatted = DateTime.Now.ToString("yyyyMMdd_hhmmss_ff");
            string backupFilename = string.Format("{0}.backup{1}",this._trackingFilePath,dateformatted);

            string message = string.Format("Backing up XML file to {0}", backupFilename);
            HDCFileSystemWatcher.Message(message, TraceLevel.Warning);
            File.Copy(this._trackingFilePath, backupFilename);
        }

    }


}
